package main

import (
	"errors"
	"fmt"
	"math/rand"
	"sort"
	"sync"
	"time"
)

type Game struct {
	sync.Mutex
	//牌
	Pokers []int
	//牌的下标
	PokerIndex int
	//庄
	Turn int
	//玩家
	Players [4]*Player
}

func NewGame() *Game {
	g := new(Game)
	return g
}

func (g *Game) Join(p *Player) error {
	g.Mutex.Lock()
	defer g.Mutex.Unlock()
	for i, j := range g.Players {
		if j != nil && j.Id == p.Id {
			return errors.New("player is joined")
		}
		if j == nil {
			g.Players[i] = p
			p.SetGame(g)
			return nil
		}
	}
	return errors.New("players is full")
}

func (g *Game) initGame() {
	rand.Seed(time.Now().UnixNano())
	//随机选庄
	g.Turn = rand.Intn(4)
	//生成麻将1-9条子 11-19筒子 21-29万字
	for i := 1; i < 10; i++ {
		g.Pokers = append(g.Pokers, i)
		g.Pokers = append(g.Pokers, i+10)
		g.Pokers = append(g.Pokers, i+20)
	}
	g.Pokers = append(g.Pokers, g.Pokers...)
	g.Pokers = append(g.Pokers, g.Pokers...)
	//洗牌
	sort.Slice(g.Pokers, func(i, j int) bool {
		return rand.Float64() > 0.5
	})
}

func (g *Game) deal() {
	//发牌
	for _, p := range g.Players {
		pk := g.Pokers[g.PokerIndex : g.PokerIndex+13]
		g.PokerIndex = g.PokerIndex + 13
		p.SetPokers(pk)
	}
	g.PokerIndex++
}

func (g *Game) startCheck() bool {
	for _, j := range g.Players {
		if !j.IsReady {
			return false
		}
	}
	return true
}

func (g *Game) dealNextOne() {
	//抓牌
	g.Players[g.Turn%4].SetCurrentHold(g.Pokers[g.PokerIndex])
	g.PokerIndex++
}

func (g *Game) Start() error {
	if !g.startCheck() {
		return errors.New("have a player is does't ready")
	}
	g.initGame()
	g.deal()
	g.dealNextOne()
	return nil
}

type Player struct {
	//用户id
	Id int
	//用户手持的牌
	Pokers []int
	//用户是否准备开始游戏
	IsReady bool
	//
	Game *Game
	//用户当前摸的牌
	CurrentHold int
}

func NewPlayer(id int) *Player {
	p := new(Player)
	p.Id = id
	return p
}

func (p Player) getAllPokers() []int {
	pk := []int{}
	pk = append(pk, p.Pokers...)
	pk = append(pk, p.CurrentHold)
	return pk
}

func (p *Player) isCanWin() bool {
	pk := p.getAllPokers()
	return CheckPokerIsWin(pk)
}

func (p *Player) isCanGang() []int {
	return nil
}

func (p *Player) SetCurrentHold(pk int) {
	p.CurrentHold = pk
	//判断玩家是否可以胡牌
	if p.isCanWin() {
		fmt.Println("可以胡牌了")
	}
	//判断玩家是否有牌可以杠
	//TODO: 通知玩家摸到的牌以及出牌等相关操作
}

func (p *Player) SetPokers(pk []int) {
	p.Pokers = pk
	//TODO: 通知玩家首牌
}

func (p *Player) SetGame(g *Game) {
	p.Game = g
}

//胡牌算法
func CheckPokerIsWin(pk []int) bool {
	numap := make(map[int]int)
	ptc := make(map[int]int)
	for _, j := range pk {
		numap[j]++
		if j < 10 {
			ptc[0]++
		} else if j > 20 {
			ptc[20]++
		} else {
			ptc[10]++
		}
	}

	//七队判断
	double := 0
	for _, v := range numap {
		if v == 2 {
			double++
		}
	}
	if double == 7 {
		return true
	}
	//如果一对将在条子里面，那么条子牌的数量要满足扣将后是三的倍数或者是0，筒子与万字数量必须是三的倍数或者是0
	if ptc[0]%3 != 0 && ptc[10]%3 == 0 && ptc[20]%3 == 0 {
		for k, v := range numap {
			if k < 10 && v >= 2 {
				numap[k] -= 2
				if makeZero(numap) {
					return true
				}
				numap[k] += 2
			}
		}
	} else if ptc[0]%3 == 0 && ptc[10]%3 != 0 && ptc[20]%3 == 0 {
		for k, v := range numap {
			if k > 10 && k < 20 && v >= 2 {
				numap[k] -= 2
				if makeZero(numap) {
					return true
				}
				numap[k] += 2
			}
		}
	} else if ptc[0]%3 == 0 && ptc[10]%3 == 0 && ptc[20]%3 != 0 {
		for k, v := range numap {
			if k > 20 && v >= 2 {
				numap[k] -= 2
				if makeZero(numap) {
					return true
				}
				numap[k] += 2
			}
		}
	}

	return false
}

//扣了一对将后，剩下的牌我们只关注最小的那张牌，这张牌要么能组成顺子，要么有三张，否则不能胡
//如果最小的那张牌能组成顺子或者有三张，那么把数量扣掉，继续找最小的那张 这里用到递归
//为什么叫make_zero，希望能通过这个方式把所有的牌的数量归0，归0就代表能胡牌，hah
func makeZero(numap map[int]int) bool {
	count := 0
	for _, v := range numap {
		count += v
	}
	if count == 0 {
		return true
	}
	min := 100
	for k, v := range numap {
		if k < min && v > 0 {
			min = k
		}
	}
	if numap[min] >= 3 {
		numap[min] -= 3
		return makeZero(numap)
	}
	v, ok := numap[min+1]
	v2, ok2 := numap[min+2]
	if ok && ok2 && v > 0 && v2 > 0 {
		numap[min]--
		numap[min+1]--
		numap[min+2]--
		return makeZero(numap)
	}
	return false
}
func main() {
	g := NewGame()
	p := NewPlayer(1)
	p2 := NewPlayer(2)
	p3 := NewPlayer(3)
	p4 := NewPlayer(4)
	g.Join(p)
	g.Join(p2)
	g.Join(p3)
	g.Join(p4)
	p.IsReady = true
	p2.IsReady = true
	p3.IsReady = true
	p4.IsReady = true
	err := g.Start()
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(p.Pokers)
}
